// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdint.h>

#include "cc/layers/picture_image_layer.h"
#include "cc/layers/solid_color_layer.h"
#include "cc/test/layer_tree_pixel_resource_test.h"
#include "cc/test/pixel_comparator.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkSurface.h"

#if !defined(OS_ANDROID)

namespace cc {
namespace {

    SkBlendMode const kBlendModes[] = {
        SkBlendMode::kSrcOver, SkBlendMode::kScreen,
        SkBlendMode::kOverlay, SkBlendMode::kDarken,
        SkBlendMode::kLighten, SkBlendMode::kColorDodge,
        SkBlendMode::kColorBurn, SkBlendMode::kHardLight,
        SkBlendMode::kSoftLight, SkBlendMode::kDifference,
        SkBlendMode::kExclusion, SkBlendMode::kMultiply,
        SkBlendMode::kHue, SkBlendMode::kSaturation,
        SkBlendMode::kColor, SkBlendMode::kLuminosity
    };

    SkColor kCSSTestColors[] = {
        0xffff0000, // red
        0xff00ff00, // lime
        0xff0000ff, // blue
        0xff00ffff, // aqua
        0xffff00ff, // fuchsia
        0xffffff00, // yellow
        0xff008000, // green
        0xff800000, // maroon
        0xff000080, // navy
        0xff800080, // purple
        0xff808000, // olive
        0xff008080, // teal
        0xfffa8072, // salmon
        0xffc0c0c0, // silver
        0xff000000, // black
        0xff808080, // gray
        0x80000000, // black with transparency
        0xffffffff, // white
        0x80ffffff, // white with transparency
        0x00000000 // transparent
    };

    const int kBlendModesCount = arraysize(kBlendModes);
    const int kCSSTestColorsCount = arraysize(kCSSTestColors);

    using RenderPassOptions = uint32_t;
    const uint32_t kUseMasks = 1 << 0;
    const uint32_t kUseAntialiasing = 1 << 1;
    const uint32_t kUseColorMatrix = 1 << 2;
    const uint32_t kForceShaders = 1 << 3;

    class LayerTreeHostBlendingPixelTest : public LayerTreeHostPixelResourceTest {
    public:
        LayerTreeHostBlendingPixelTest()
            : force_antialiasing_(false)
            , force_blending_with_shaders_(false)
        {
            pixel_comparator_.reset(new FuzzyPixelOffByOneComparator(true));
        }

        void InitializeSettings(LayerTreeSettings* settings) override
        {
            settings->renderer_settings.force_antialiasing = force_antialiasing_;
            settings->renderer_settings.force_blending_with_shaders = force_blending_with_shaders_;
        }

    protected:
        void RunBlendingWithRootPixelTestType(PixelResourceTestCase type)
        {
            const int kLaneWidth = 2;
            const int kLaneHeight = kLaneWidth;
            const int kRootWidth = (kBlendModesCount + 2) * kLaneWidth;
            const int kRootHeight = 2 * kLaneWidth + kLaneHeight;
            InitializeFromTestCase(type);

            scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(gfx::Rect(kRootWidth, kRootHeight), kCSSOrange);

            // Orange child layers will blend with the green background
            for (int i = 0; i < kBlendModesCount; ++i) {
                gfx::Rect child_rect(
                    (i + 1) * kLaneWidth, kLaneWidth, kLaneWidth, kLaneHeight);
                scoped_refptr<SolidColorLayer> green_lane = CreateSolidColorLayer(child_rect, kCSSGreen);
                background->AddChild(green_lane);
                green_lane->SetBlendMode(kBlendModes[i]);
            }

            RunPixelResourceTest(
                background,
                base::FilePath(FILE_PATH_LITERAL("blending_with_root.png")));
        }

        void RunBlendingWithTransparentPixelTestType(PixelResourceTestCase type)
        {
            const int kLaneWidth = 2;
            const int kLaneHeight = 3 * kLaneWidth;
            const int kRootWidth = (kBlendModesCount + 2) * kLaneWidth;
            const int kRootHeight = 2 * kLaneWidth + kLaneHeight;
            InitializeFromTestCase(type);

            scoped_refptr<SolidColorLayer> root = CreateSolidColorLayer(gfx::Rect(kRootWidth, kRootHeight), kCSSBrown);

            scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
                gfx::Rect(0, kLaneWidth * 2, kRootWidth, kLaneWidth), kCSSOrange);

            root->AddChild(background);
            background->SetIsRootForIsolatedGroup(true);

            // Orange child layers will blend with the green background
            for (int i = 0; i < kBlendModesCount; ++i) {
                gfx::Rect child_rect(
                    (i + 1) * kLaneWidth, -kLaneWidth, kLaneWidth, kLaneHeight);
                scoped_refptr<SolidColorLayer> green_lane = CreateSolidColorLayer(child_rect, kCSSGreen);
                background->AddChild(green_lane);
                green_lane->SetBlendMode(kBlendModes[i]);
            }

            RunPixelResourceTest(
                root, base::FilePath(FILE_PATH_LITERAL("blending_transparent.png")));
        }

        scoped_refptr<Layer> CreateColorfulBackdropLayer(int width, int height)
        {
            // Draw the backdrop with horizontal lanes.
            const int kLaneWidth = width;
            const int kLaneHeight = height / kCSSTestColorsCount;
            sk_sp<SkSurface> backing_store = SkSurface::MakeRasterN32Premul(width, height);
            SkCanvas* canvas = backing_store->getCanvas();
            canvas->clear(SK_ColorTRANSPARENT);
            for (int i = 0; i < kCSSTestColorsCount; ++i) {
                SkPaint paint;
                paint.setColor(kCSSTestColors[i]);
                canvas->drawRect(
                    SkRect::MakeXYWH(0, i * kLaneHeight, kLaneWidth, kLaneHeight), paint);
            }
            scoped_refptr<PictureImageLayer> layer = PictureImageLayer::Create();
            layer->SetIsDrawable(true);
            layer->SetBounds(gfx::Size(width, height));
            layer->SetImage(backing_store->makeImageSnapshot());
            return layer;
        }

        void SetupMaskLayer(scoped_refptr<Layer> layer)
        {
            const int kMaskOffset = 2;
            gfx::Size bounds = layer->bounds();
            scoped_refptr<PictureImageLayer> mask = PictureImageLayer::Create();
            mask->SetIsDrawable(true);
            mask->SetIsMask(true);
            mask->SetBounds(bounds);

            sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(bounds.width(), bounds.height());
            SkCanvas* canvas = surface->getCanvas();
            SkPaint paint;
            paint.setColor(SK_ColorWHITE);
            canvas->clear(SK_ColorTRANSPARENT);
            canvas->drawRect(SkRect::MakeXYWH(kMaskOffset, kMaskOffset,
                                 bounds.width() - kMaskOffset * 2,
                                 bounds.height() - kMaskOffset * 2),
                paint);
            mask->SetImage(surface->makeImageSnapshot());
            layer->SetMaskLayer(mask.get());
        }

        void SetupColorMatrix(scoped_refptr<Layer> layer)
        {
            FilterOperations filter_operations;
            filter_operations.Append(FilterOperation::CreateSepiaFilter(.001f));
            layer->SetFilters(filter_operations);
        }

        void CreateBlendingColorLayers(int lane_width,
            int lane_height,
            scoped_refptr<Layer> background,
            RenderPassOptions flags)
        {
            const int kLanesCount = kBlendModesCount + 4;
            const SkColor kMiscOpaqueColor = 0xffc86464;
            const SkColor kMiscTransparentColor = 0x80c86464;
            const SkBlendMode kCoeffBlendMode = SkBlendMode::kScreen;
            const SkBlendMode kShaderBlendMode = SkBlendMode::kColorBurn;
            // add vertical lanes with each of the blend modes
            for (int i = 0; i < kLanesCount; ++i) {
                gfx::Rect child_rect(i * lane_width, 0, lane_width, lane_height);
                SkBlendMode blend_mode = SkBlendMode::kSrcOver;
                float opacity = 1.f;
                SkColor color = kMiscOpaqueColor;

                if (i < kBlendModesCount) {
                    blend_mode = kBlendModes[i];
                } else if (i == kBlendModesCount) {
                    blend_mode = kCoeffBlendMode;
                    opacity = 0.5f;
                } else if (i == kBlendModesCount + 1) {
                    blend_mode = kCoeffBlendMode;
                    color = kMiscTransparentColor;
                } else if (i == kBlendModesCount + 2) {
                    blend_mode = kShaderBlendMode;
                    opacity = 0.5f;
                } else if (i == kBlendModesCount + 3) {
                    blend_mode = kShaderBlendMode;
                    color = kMiscTransparentColor;
                }

                scoped_refptr<SolidColorLayer> lane = CreateSolidColorLayer(child_rect, color);
                lane->SetBlendMode(blend_mode);
                lane->SetOpacity(opacity);
                lane->SetForceRenderSurfaceForTesting(true);
                if (flags & kUseMasks)
                    SetupMaskLayer(lane);
                if (flags & kUseColorMatrix) {
                    SetupColorMatrix(lane);
                }
                background->AddChild(lane);
            }
        }

        void RunBlendingWithRenderPass(PixelResourceTestCase type,
            const base::FilePath::CharType* expected_path,
            RenderPassOptions flags)
        {
            const int kLaneWidth = 8;
            const int kLaneHeight = kLaneWidth * kCSSTestColorsCount;
            const int kRootSize = kLaneHeight;
            InitializeFromTestCase(type);

            scoped_refptr<SolidColorLayer> root = CreateSolidColorLayer(gfx::Rect(kRootSize, kRootSize), SK_ColorWHITE);
            scoped_refptr<Layer> background = CreateColorfulBackdropLayer(kRootSize, kRootSize);

            background->SetIsRootForIsolatedGroup(true);
            root->AddChild(background);

            CreateBlendingColorLayers(kLaneWidth, kLaneHeight, background.get(), flags);

            this->force_antialiasing_ = (flags & kUseAntialiasing);
            this->force_blending_with_shaders_ = (flags & kForceShaders);

            if ((flags & kUseAntialiasing) && (test_type_ == PIXEL_TEST_GL)) {
                // Anti aliasing causes differences up to 8 pixels at the edges.
                int large_error_allowed = 8;
                // Blending results might differ with one pixel.
                int small_error_allowed = 1;
                // Most of the errors are one pixel errors.
                float percentage_pixels_small_error = 13.1f;
                // Because of anti-aliasing, around 10% of pixels (at the edges) have
                // bigger errors (from small_error_allowed + 1 to large_error_allowed).
                float percentage_pixels_error = 22.5f;
                // The average error is still close to 1.
                float average_error_allowed_in_bad_pixels = 1.4f;

                pixel_comparator_.reset(
                    new FuzzyPixelComparator(false, // discard_alpha
                        percentage_pixels_error,
                        percentage_pixels_small_error,
                        average_error_allowed_in_bad_pixels,
                        large_error_allowed,
                        small_error_allowed));
            }

            RunPixelResourceTest(root, base::FilePath(expected_path));
        }

        bool force_antialiasing_;
        bool force_blending_with_shaders_;
    };

    TEST_F(LayerTreeHostBlendingPixelTest, BlendingWithRoot_GL)
    {
        RunBlendingWithRootPixelTestType(GL_ZERO_COPY_RECT_DRAW);
    }

    TEST_F(LayerTreeHostBlendingPixelTest, BlendingWithRoot_Software)
    {
        RunBlendingWithRootPixelTestType(SOFTWARE);
    }

    TEST_F(LayerTreeHostBlendingPixelTest, BlendingWithBackgroundFilter)
    {
        const int kLaneWidth = 2;
        const int kLaneHeight = kLaneWidth;
        const int kRootWidth = (kBlendModesCount + 2) * kLaneWidth;
        const int kRootHeight = 2 * kLaneWidth + kLaneHeight;
        InitializeFromTestCase(GL_ZERO_COPY_RECT_DRAW);

        scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(gfx::Rect(kRootWidth, kRootHeight), kCSSOrange);

        // Orange child layers have a background filter set and they will blend with
        // the green background
        for (int i = 0; i < kBlendModesCount; ++i) {
            gfx::Rect child_rect(
                (i + 1) * kLaneWidth, kLaneWidth, kLaneWidth, kLaneHeight);
            scoped_refptr<SolidColorLayer> green_lane = CreateSolidColorLayer(child_rect, kCSSGreen);
            background->AddChild(green_lane);

            FilterOperations filters;
            filters.Append(FilterOperation::CreateGrayscaleFilter(.75));
            green_lane->SetBackgroundFilters(filters);
            green_lane->SetBlendMode(kBlendModes[i]);
        }

        RunPixelResourceTest(
            background, base::FilePath(FILE_PATH_LITERAL("blending_and_filter.png")));
    }

    TEST_F(LayerTreeHostBlendingPixelTest, BlendingWithTransparent_GL)
    {
        RunBlendingWithTransparentPixelTestType(GL_ZERO_COPY_RECT_DRAW);
    }

    TEST_F(LayerTreeHostBlendingPixelTest, BlendingWithTransparent_Software)
    {
        RunBlendingWithTransparentPixelTestType(SOFTWARE);
    }

    // Tests for render passes
    TEST_F(LayerTreeHostBlendingPixelTest, BlendingWithRenderPass_GL)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass.png"), 0);
    }

    TEST_F(LayerTreeHostBlendingPixelTest, BlendingWithRenderPass_Software)
    {
        RunBlendingWithRenderPass(SOFTWARE,
            FILE_PATH_LITERAL("blending_render_pass.png"), 0);
    }

    TEST_F(LayerTreeHostBlendingPixelTest, BlendingWithRenderPassAA_GL)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass.png"),
            kUseAntialiasing);
    }

    TEST_F(LayerTreeHostBlendingPixelTest, BlendingWithRenderPassAA_Software)
    {
        RunBlendingWithRenderPass(SOFTWARE,
            FILE_PATH_LITERAL("blending_render_pass.png"),
            kUseAntialiasing);
    }

    TEST_F(LayerTreeHostBlendingPixelTest, BlendingWithRenderPassWithMask_GL)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass_mask.png"),
            kUseMasks);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassWithMask_Software)
    {
        RunBlendingWithRenderPass(
            SOFTWARE, FILE_PATH_LITERAL("blending_render_pass_mask.png"), kUseMasks);
    }

    TEST_F(LayerTreeHostBlendingPixelTest, BlendingWithRenderPassWithMaskAA_GL)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass_mask.png"),
            kUseMasks | kUseAntialiasing);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassWithMaskAA_Software)
    {
        RunBlendingWithRenderPass(SOFTWARE,
            FILE_PATH_LITERAL("blending_render_pass_mask.png"),
            kUseMasks | kUseAntialiasing);
    }

    TEST_F(LayerTreeHostBlendingPixelTest, BlendingWithRenderPassColorMatrix_GL)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass.png"),
            kUseColorMatrix);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassColorMatrix_Software)
    {
        RunBlendingWithRenderPass(
            SOFTWARE, FILE_PATH_LITERAL("blending_render_pass.png"), kUseColorMatrix);
    }

    TEST_F(LayerTreeHostBlendingPixelTest, BlendingWithRenderPassColorMatrixAA_GL)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass.png"),
            kUseAntialiasing | kUseColorMatrix);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassColorMatrixAA_Software)
    {
        RunBlendingWithRenderPass(SOFTWARE,
            FILE_PATH_LITERAL("blending_render_pass.png"),
            kUseAntialiasing | kUseColorMatrix);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassWithMaskColorMatrix_GL)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass_mask.png"),
            kUseMasks | kUseColorMatrix);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassWithMaskColorMatrix_Software)
    {
        RunBlendingWithRenderPass(SOFTWARE,
            FILE_PATH_LITERAL("blending_render_pass_mask.png"),
            kUseMasks | kUseColorMatrix);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassWithMaskColorMatrixAA_GL)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass_mask.png"),
            kUseMasks | kUseAntialiasing | kUseColorMatrix);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassWithMaskColorMatrixAA_Software)
    {
        RunBlendingWithRenderPass(SOFTWARE,
            FILE_PATH_LITERAL("blending_render_pass_mask.png"),
            kUseMasks | kUseAntialiasing | kUseColorMatrix);
    }

    // Tests for render passes forcing shaders for all the blend modes.
    TEST_F(LayerTreeHostBlendingPixelTest, BlendingWithRenderPassShaders_GL)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass.png"),
            kForceShaders);
    }

    TEST_F(LayerTreeHostBlendingPixelTest, BlendingWithRenderPassShadersAA_GL)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass.png"),
            kUseAntialiasing | kForceShaders);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassShadersWithMask_GL)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass_mask.png"),
            kUseMasks | kForceShaders);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassShadersWithMask_GL_TextureRect)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass_mask.png"),
            kUseMasks | kForceShaders);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassShadersWithMaskAA_GL)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass_mask.png"),
            kUseMasks | kUseAntialiasing | kForceShaders);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassShadersWithMaskAA_GL_TextureRect)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass_mask.png"),
            kUseMasks | kUseAntialiasing | kForceShaders);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassShadersColorMatrix_GL)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass.png"),
            kUseColorMatrix | kForceShaders);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassShadersColorMatrixAA_GL)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass.png"),
            kUseAntialiasing | kUseColorMatrix | kForceShaders);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassShadersWithMaskColorMatrix_GL)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass_mask.png"),
            kUseMasks | kUseColorMatrix | kForceShaders);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassShadersWithMaskColorMatrix_GL_TextureRect)
    {
        RunBlendingWithRenderPass(GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass_mask.png"),
            kUseMasks | kUseColorMatrix | kForceShaders);
    }

    TEST_F(LayerTreeHostBlendingPixelTest,
        BlendingWithRenderPassShadersWithMaskColorMatrixAA_GL)
    {
        RunBlendingWithRenderPass(
            GL_ZERO_COPY_RECT_DRAW,
            FILE_PATH_LITERAL("blending_render_pass_mask.png"),
            kUseMasks | kUseAntialiasing | kUseColorMatrix | kForceShaders);
    }

} // namespace
} // namespace cc

#endif // OS_ANDROID
